# 
#  KnowledgeEngineTool.py
#  KnowledgeEngine
#  
#  Created by John Meredith on 2007-02-15.
#  Copyright 2007 Faulkner Technologies. All rights reserved.
# 
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass
from OFS.SimpleItem import SimpleItem
from Products.CMFCore.utils import UniqueObject
from Products.PageTemplates.PageTemplateFile import PageTemplateFile

from Common import *
from Core import *
from Permission import *
from Model.Model import Model
from Components.MultipleChoice import MultipleChoice

from DateTime import DateTime

from Permission import Permission

import types
from urllib import unquote

# XML Helper, should probably be moved somewhere more usefull
def getChildrenByTagName( dom, tagName ):
    """ Returns all children of the given tag name """
    
    if dom:
        for node in dom.childNodes:
            if getattr(node, 'tagName', None) == tagName:
                yield node

def getFirstChildByTagName( dom, tagName ):
    """ Returns the first direct child of the given tag name, or `None` if not found """

    children = getChildrenByTagName( dom, tagName )
    
    # Iterate over the list, but return on the first one found. If there are no matching children, returns None
    for child in children:
        return child

    return None

class KnowledgeEngineTool(UniqueObject, SimpleItem):
    """
        KnowledgeEngine configuration tool
    """

    id = "knowledgeengine_tool"
    meta_type = "KnowledgeEngine Tool"
    title = "KnowledgeEngine Configuration Tool"
    security = ClassSecurityInfo()
    
    permissions = PageTemplateFile("Cache/knowledgeengine_permissions.pt", globals())
    permissions.title = 'KnowledgeEngine Global Permissions'

    # ------------------------------------------------------------------------------
    
    def getAssessmentsForUser( self, user=None, modelIdentities=None, repositoryIdentities=None ):
        """ Returns assessments for the specified user """
        
    
                
        security                 = self.knowledgeengine_securityservice
        assessmentStorageService = self.knowledgeengine_assessmentstorageservice
        userProviderService      = self.knowledgeengine_userproviderservice
        
        if user is None:
            user = userProviderService.getUser( self.REQUEST.AUTHENTICATED_USER.getId() )

        elif type(user) is str:
            user = userProviderService.getUser( user )

        
        allmodels = security.getModels()
        
        
        if modelIdentities:
            models = [self.getComponentByIdentity( identity ) for identity in modelIdentities]
        else:
            models = [model for model in allmodels if (model.isAccessibleByUser(user) and model.hasUserAccessibleRepositories(user))]

        assessments = []

        

        for model in models:
            
            if (not repositoryIdentities) or (user.getIdentity() in repositoryIdentities):
                personalAssessments = assessmentStorageService.getAssessmentsInRepository(model, user.getIdentity())
                assessments.extend( personalAssessments )
            
            repositories = model.getRepositories()
            
            if repositoryIdentities:
                repositories = [repo for repo in repositories if repo.getIdentity() in repositoryIdentities]
                
                
            
            for repository in repositories:
                #repositoryAssessments = assessmentStorageService.getAssessmentsInRepository(model, repository.getIdentity())
                repositoryAssessments = repository.getAvailableAssessments( user.getIdentity() )
                        
                assessments.extend( repositoryAssessments )
                
                
        return assessments
        
    
    # ------------------------------------------------------------------------------

    # todo - move this
    def xmlize(self, string):
        """ Serialize values to an XML Capable string """
        
       
        tokens = { None: "_x_NONE", True: "_x_TRUE", False: "_x_FALSE" }
        
        if type(string) in (list, tuple): return "_x_LIST-%s" % str(string)
        
        if type(string) is DateTime: 
            return "_x_DATE-%s" % str(string)
        
        if type(string) is int:
            return "_x_INT-%s" % str(string)
        
        if type(string)is float:
            return "_x_FLOAT-%s" % str(string)
        
        if string in tokens:
            return tokens[string]
                         
        return str(string)
                         
        #return string.encode('utf8')
    
    def unxmlize( self, string ):
        """ Unserializes a value serialized by xmlize """
        
        tokens = { "_x_NONE": None, "_x_TRUE": True, "_x_FALSE": False }
        
        if string in tokens:            
            r = tokens[string]
            print "Token is %s, returning %s" % ( `string`, `r` )
            return r
        
        print " Ignoring token? %s" % `string`
        
        # HACK
        if string.startswith("_x_LIST-"):
            return eval( string[8:] )
        
        if string.startswith("_x_DATE-"):
            return DateTime( string[8:] )
        
        if string.startswith("_x_INT-"):
            return int( string[7:] )
        
        if string.startswith("_x_FLOAT-"):
            return float(string[9:])
        
        return string.encode('utf8')

    # ------------------------------------------------------------------------------

    security.declareProtected('Manage portal', 'importObjectFromXML')
    def importObjectFromXML( self, root, xml, recalculateIdentities=False ):
        """ Imports a KE XML File """
        
        if xml.startswith('begin'):
            # File is ZUU Encoded
            import uu, zlib, cStringIO 
            
            uutext = xml
            
            
            fin   = cStringIO.StringIO() # "File IN"  for uuencoding
            fout  = cStringIO.StringIO() # "File OUT" for uuencoding
            
            # Put the compressed xml data into the input buffer
            print >> fin, xml
            fin.seek(0) # Reset buffer to start
            
            # un-UUEncode the data and write to the output buffer
            uu.decode( fin, fout )
            xmlz = fout.getvalue()            
            xml   = zlib.decompress( xmlz )

                               
        from xml.dom import minidom
        dom = minidom.parseString( xml )
        
        return self.importObjectFromDOM( root, dom, recalculateIdentities=recalculateIdentities)
    
    # ------------------------------------------------------------------------------
    
    security.declareProtected('Manage portal', 'importObjectFromDOM')
    def importObjectFromDOM(self, root, dom, recalculateIdentities=False):
        """ Import XML From DOM Tree """

        
        if getattr(dom, 'tagName', None) == 'object':
            objects = [dom]
        else:
            objects = getChildrenByTagName(dom, 'object')
        
        unxmlize = self.unxmlize
        
        imported = []
        
        
        for object in objects:
        
            className  = unxmlize(object.getAttribute('class'))
            id         = unxmlize(object.getAttribute('zopeid'))
            keidentity = unxmlize(object.getAttribute('identity'))
            
            propertyMap = {}
            
            unxmlize = self.unxmlize
            
            security = self.knowledgeengine_securityservice
            
            # Extract permissions
            for node in getChildrenByTagName(object, 'permissions'):                 
                for property in node.getElementsByTagName('permission'):
            
                    identity             = unxmlize(property.getAttribute( 'permissionIdentity' ))
                    securableIdentity    = unxmlize(property.getAttribute( 'securableIdentity' ))
                    actorIdentity        = unxmlize(property.getAttribute( 'actorIdentity' ))
                    isGranted            = unxmlize(property.getAttribute( 'isGranted' ))                    
                    instantEffectiveFrom = unxmlize(property.getAttribute( 'instantEffectiveFrom' ))
                    instantExpiresOn     = unxmlize(property.getAttribute( 'instantExpiresOn' ))
                    creditStrategy       = unxmlize(property.getAttribute( 'creditStrategy' ))
                    assessmentScope      = unxmlize(property.getAttribute( 'assessmentScope' ))                    
                    pythonFilter         = unxmlize(property.getAttribute( 'pythonFilter' ))
                                                    
                    permission = Permission( identity, securableIdentity, actorIdentity, isGranted, instantEffectiveFrom, instantExpiresOn, creditStrategy, assessmentScope )
                    permission.setPythonFilter( pythonFilter )
                    security.savePermission( permission )
            
            
            # Extract properties
            for node in getChildrenByTagName(object, 'properties'):
                for property in node.getElementsByTagName('property'):
                    identity = unxmlize(property.getAttribute('identity'))
                    ptype    = unxmlize(property.getAttribute('type'))
                    
                    # Force property values to UTF8 as users often paste "Smart Quotes" from MS Word
                    value    = unxmlize( property.getAttribute('value').encode('utf8') )
                                  
                    if ptype == "ANSWER_LIST":
                        answers=  []
                        for answerNode in property.getElementsByTagName('answer'):
                            answer = MultipleChoice.Answer( 
                                answerID            = unxmlize(answerNode.getAttribute('answerID')),
                                content             = unxmlize(answerNode.getAttribute('content')),
                                assessmentContent   = unxmlize(answerNode.getAttribute('assessmentContent')),
                                reportContent       = unxmlize(answerNode.getAttribute('reportContent')),
                                score               = float(unxmlize(answerNode.getAttribute('score'))),
                                isCorrectValue      = answerNode.getAttribute('isCorrectValue') == "True",
                                option              = unxmlize(answerNode.getAttribute('option')),
                            )
                        
                            answers.append( answer )
                                                                   
                        value = answers
                                                                                           
                    #else:
                    #    raise "Unknown property type: %s = %s" % ( ptype, value )
                    
                    propertyMap[ identity ] = value
                        
                break # Stop looking for property parent 
                    
                    
            propertyMap['id'] = id                
                    
            print "Creating %s '%s'" % (className, id)
                            
            #if className == 'Model':  
            if not hasattr(root, '_CLASSINFO'):
                # Models need to be created in Plone, not within an existing GenericObject based container
                
                if className == 'Model':
                    obj = Model( id, keidentity, createTemplate=False )
                else:                                        
                    classInfo = getClassInfo(className)
                    obj = classInfo.newInstance( id, keidentity)
                                
                root._setObject( id, obj )
                genericObject = getattr(root, id)
                
                genericObject._setPortalTypeName(className)
                genericObject.notifyWorkflowCreated() 
                #root.invokeFactory( type_name=className, id=id )
            else:                                
                result = root.createObject( CLASSNAME=className, PROPERTIES=propertyMap, REQUEST=None, IGNORE_CONSTRUCTOR=True, id=id, identity=keidentity )
                genericObject = getattr(root, id, None)
                
                if not genericObject:
                    return
                
            for property, value in propertyMap.items():
                setattr( genericObject, property, value )                
                
            genericObject.setTitle( propertyMap.get('title','') )

            if recalculateIdentities:
                genericObject.onAfterClone()
                
            genericObject.reindexObject()
            children = getFirstChildByTagName(object, 'children')
            self.importObjectFromDOM( genericObject, children, recalculateIdentities=recalculateIdentities )
                    
            imported.append( genericObject )

        return imported
            
    # ------------------------------------------------------------------------------            
        
        


    # ------------------------------------------------------------------------------
    
    security.declareProtected('Manage portal', 'free_reight')
    def free_reign(self, REQUEST, **args):
        """
            Mass enable all permissions for all object types
        """
        
        # We'll need this
        securityService = getSecurityService(self)

        # If we have an object type selected, ensure we reset it after we've finished
        permissionableObjectType = REQUEST.form.get('permissionableObjectType', None)
        if REQUEST.form.has_key('form.button.AllowAll') and permissionableObjectType:
            keObject = securityService.getObjectByName(REQUEST.form['permissionableObjectType'])

            for definition in keObject.getClassInfo().getPermissionDefinitions():
                
                # Add a new permission if it doesn't already exist
                if not securityService.getSpecificPermission(definition.getIdentity(), '*', '*'):
                    if definition.getUsesAssessmentScope():
                        permission = Permission(definition.getIdentity(), '*', '*', True, assessmentScope='*')
                    else:
                        permission = Permission(definition.getIdentity(), '*', '*', True)
                    
                    securityService.savePermission( permission )

        return self.permissions(self, REQUEST, permissionableObjectType=permissionableObjectType)


    # ------------------------------------------------------------------------------
    def permission_form(self, REQUEST, **args):
        """
            The purpose of this method is the handle BOTH the GET and POST
            request for editing component permissions via the
            knowledgeengine_permisison_form.pt
        """

        #let's use a sensible case for the REQUEST
        request = REQUEST

        #let's get the session from the request
        session = request.SESSION

        #get the user id of the currently authenticated user
        zopeUserIdentity = request["AUTHENTICATED_USER"].getId()
        userIdentity = iif(zopeUserIdentity is None, "anonymous", zopeUserIdentity)

        #get the User from the UserProviderService (we may even store it in the session!)
        userProviderService = getUserProviderService(self)
        user = userProviderService.getUser(userIdentity)

        #we assume the specific permission is undefined
        permission = None

        #let's determine the permission (definition) we'll be displaying

        permissionableObjectType = None
        if request.form.has_key('permissionableObjectType'):
            permissionableObjectType = request['permissionableObjectType']

        #get the permission identity we are displaying/editing
        if request.form.has_key("permissionIdentity"):
            permissionIdentity = request["permissionIdentity"]
        else:
            permissionIdentity = request["cmbPermissionDefinition"]

        #handle a delete request
        if request.form.has_key("form.button.Delete"):
            #get the security service so we can look up permissions
            securityService = getSecurityService(self)

            #get all of the permissions so we can determine which one(s) to delete
            permissions = securityService.getPermissions('*')

            #delete the selected permissions (that are in the request)
            for aPermission in permissions:

                #is the current permission in the request to be deleted?
                if request.form.has_key("chkPermission%s%s%s" % (aPermission.getIdentity(), aPermission.getSecurableIdentity(), aPermission.getActorIdentity())):
                    securityService.removePermission(aPermission)

            #after delete go back to the 'DISPLAY' mode
            mode = 'DISPLAY'

        #was add requested?
        elif request.form.has_key("form.button.Add"):
            #create a template permission to edit
            permission = Permission(permissionIdentity, self.getIdentity(), "*", true)

            #we are now in the 'CREATE' mode
            mode = 'CREATE'

        #was save requested
        elif request.form.has_key("form.button.Save"):
            #get the security service so we can look up the permission to edit
            securityService = getSecurityService(self)

            #get the existing permission (if we are editing)
            if request["mode"] == "EDIT":
                permission = securityService.getSpecificPermission(permissionIdentity, request["originalSecurableIdentity"], request["originalActorIdentity"])

            else:
                #get the securable identity
                securableIdentity = request["cmbSecurableIdentity"]

                #get the actor identity (we may have to get the specific actor!)
                actorIdentity = request["cmbActorIdentity"]
                if actorIdentity not in ["*", "anonymous"]:
                    # actorIdentity = request["actorIdentity"]
                    actorIdentity = request["cmbActor"]

                #attempt to get the existing permission
                permission = securityService.getSpecificPermission(permissionIdentity, securableIdentity, actorIdentity)

                #create a permission if one doesn't already exist
                if permission is None:
                    permission = Permission(permissionIdentity, securableIdentity, actorIdentity, true)

            #set the attributes of the permission from the request
            if request["cmbStatus"] == "granted":
                permission.grant()
            else:
                permission.deny()

            #attempt to set the effective from time
            try:
                permission.setInstantEffectiveFrom(DateTime(request["effectiveFrom"]))
            except:
                permission.setInstantEffectiveFrom(None)

            #set the expiry time
            try:
                permission.setInstantExpiresOn(DateTime(request["expiresOn"]))
            except:
                permission.setInstantExpiresOn(None)

            #set the credit management strategy
            try:
                permission.setCreditStrategy(request["cmbCreditStrategy"])
            except:
                permission.setCreditStrategy(CS_NOT_APPLICABLE)

            #set the assessmentscope
            try:
                #get the selected list for the assessment scope
                lstAssessmentScope = request["lstAssessmentScope"]
        
                #was a list of items selected?
                if type(lstAssessmentScope) is types.ListType:
                    #ensure that if '*' is selected, nothing else is selected
                    if '*' in lstAssessmentScope:
                        permission.setAssessmentScope(['*'])
                    else:
                        permission.setAssessmentScope(lstAssessmentScope)
                else:
                    permission.setAssessmentScope([lstAssessmentScope])                        

            except:
                permission.setAssessmentScope([])

            #save the changed permission (will create a new one if it doesn't already exist)
            securityService.savePermission(permission)

            #we are now in the 'DISPLAY' mode
            mode = 'DISPLAY'

        #was cancel requested?
        elif request.form.has_key("form.button.Cancel"):
            #we are now in the 'DISPLAY' mode
            mode = 'DISPLAY'

        #was edit requested
        elif request.form.has_key("mode") and request["mode"] == "EDIT":
            #get the security service so we can look up the permission to edit
            securityService = getSecurityService(self)

            #get the permission to edit
            permission = securityService.getSpecificPermission(request["permissionIdentity"], request["securableIdentity"], request["actorIdentity"])

            #we are now in the 'EDIT' mode
            mode = 'EDIT'

        #was credit management requested?
        elif request.form.has_key("mode") and request["mode"] == "MANAGECREDITS":
            #get the security service so we can look up the permission to edit
            securityService = getSecurityService(self)

            #get the permission for which to manage credits
            permission = securityService.getSpecificPermission(request["permissionIdentity"], request["securableIdentity"], request["actorIdentity"])

            #are we making a deposit/withdraw?
            if request.form.has_key("form.button.Deposit") or request.form.has_key("form.button.Withdraw"):
                #get the credit management service
                creditManagementService = getCreditManagementService(self)
            
                #get the narration and amount
                narration = request["txtNarration"]
                try:
                    amount = int(request["numAmount"])
                    if amount < 0:
                        amount = 0
                except:
                    amount = 0
                    
                #if we have an amount
                if amount != 0:
                    if request.form.has_key("form.button.Deposit"):
                        #are we depositing for a group?                        
                        if request.form.has_key("optCreditOption"):
                            if request["optCreditOption"] == "SHARED":
                                #credits are shared by the group members so we define the credit on the group itself
                                actorIdentities = [permission.getActorIdentity()]
                            else:
                                #credits are for each member of the group, so the need to apply the credit to each group member
                                groupProviderService = getGroupProviderService(self)
                                actorIdentities = groupProviderService.getUserIdentitiesInGroup(permission.getActorIdentity())                                    
                        else:
                            actorIdentities = [permission.getActorIdentity()]
                    
                        #do the deposit for all of the actors
                        for actorIdentity in actorIdentities:
                            creditManagementService.depositCredits(permission.getIdentity(), permission.getSecurableIdentity(), actorIdentity, user.getIdentity(), amount, narration)
                        
                    elif request.form.has_key("form.button.Withdraw"):
                        #are we withdrawing for a group?                        
                        if request.form.has_key("optCreditOption"):
                            if request["optCreditOption"] == "SHARED":
                                #credits are shared by the group members so we define the credit on the group itself
                                actorIdentities = [permission.getActorIdentity()]
                            else:
                                #credits are for each member of the group, so the need to apply the credit to each group member
                                groupProviderService = getGroupProviderService(self)
                                actorIdentities = groupProviderService.getUserIdentitiesInGroup(permission.getActorIdentity())                                    
                        else:
                            actorIdentities = [permission.getActorIdentity()]

                        #do the withdrawal for all of the actors
                        for actorIdentity in actorIdentities:
                        
                            #ensure we don't overdraw!
                            balanceAvailable = creditManagementService.getAvailableCredits(permission.getIdentity(), permission.getSecurableIdentity(), actorIdentity)                                
                            if balanceAvailable < amount:
                                amount = balanceAvailable
                        
                            #do the withdraw for the actor
                            creditManagementService.withdrawCredits(permission.getIdentity(), permission.getSecurableIdentity(), actorIdentity, user.getIdentity(), amount, narration)

            #we are now in the 'MANAGECREDITS' mode
            mode = 'MANAGECREDITS'

        else:
            #some weird request... let's default to 'DISPLAY'
            mode = 'DISPLAY'

        #render the permission editor form
        return self.permissions(self, REQUEST, MODE=mode, ISCONSTRUCTOR=(mode != 'DISPLAY'), permissionableObjectType=permissionableObjectType, PERMISSION=permission, getToolByName=getToolByName)

InitializeClass(KnowledgeEngineTool)